module(..., package.seeall)

local NodeTreeTrans = {}
local _mtkey = {}
local NodeTreeTrans_mt = {
	__metatable = _mtkey,
	__index = function(t,key)
		local i=NodeTreeTrans[key]
		if i~=nil then return i end
		if t._delkeys and t._delkeys[key] then
			i=t:__get_field_define(key)
			if i then
				local dyn=i.dyn
				if dyn then
					if type(dyn)~='function' then
						dyn=assert(loadactfun(t.__dpl.dal,t,dyn),'can not load act '..tostring(dyn))
					end
					return dyn(nil,t.__dpl.dal,t,key)
				end
				return t:__get_pto_field(key,i)
			end
			return
		end
		if t._newobj then
			i=t._newobj[key]
			if i~=nil then return i end
		end
		i=(t._subobj and t._subobj[key]) or t:__autowrapsub(key)
		if i then return i end
		return t._orgobj[key]
	end,
	
}

function _edit(t,key,nv)
	if nil==nv then
		t:_delete(key)
		if t._newobj then t._newobj[key]=nil end
		if t._subobj then t._subobj[key]=nil end
	else
		b,pt=t:__is_proto_val(key)
		if b or (t._newobj and t._newobj[key]~=nil) then
			if not t._newobj then t._newobj={} end
			t._newobj[key] = wrap(nv)
			if t._delkeys then t._delkeys[key]=nil end
		elseif pt ~= nv then
			if not t._newobj then t._newobj={} end
			t._newobj[key] = wrap(nv)
			if t._delkeys then t._delkeys[key]=nil end
		end
	end
end
NodeTreeTrans_mt.__newindex=_edit 

function isNodeTreeTrans(n)
	return getmetatable(n) == _mtkey
end

function wrap(obj,newadd)
	if obj==nil then return nil end
	if isNodeTreeTrans(obj) or type(obj) ~= 'table' then return obj end
	local nobj = setmetatable({_orgobj = obj,_delkeys=false,_newobj=false,_subobj=false},NodeTreeTrans_mt)
	nobj:_wrap_child(newadd)	
	return nobj
end

function NodeTreeTrans._index_r(t,key)
	local i=NodeTreeTrans[key]
	if i~=nil then return i end
	if t._delkeys and t._delkeys[key] then
		i=t:__get_field_define(key)
		if i then
			local dyn=i.dyn
			if dyn then
				if type(dyn)~='function' then
					dyn=assert(loadactfun(t.__dpl.dal,t,dyn),'can not load act '..tostring(dyn))
				end
				return dyn(nil,t.__dpl.dal,t,key)
			end
			return t:__get_pto_field(key,i)
		end
		return
	end
	if t._newobj then
		i=t._newobj[key]
		if i~=nil then return i end
	end
	i=(t._subobj and t._subobj[key])
	if i then return i end
	return t._orgobj[key]
end

function NodeTreeTrans:__autowrapsub(key)
	local v=self._subobj and self._subobj[key]
	if v then return v end
	v=self._orgobj._subnode[key]
	if v then
		if not self._subobj then self._subobj={} end
		v=wrap(v)
		self._subobj[key]=v
	end
	return v
end

function NodeTreeTrans:_wrap_child(newadd)
	if newadd then
		if next(self._orgobj._subnode) then
			local _so = {}
			for _i,_j in pairs(self._orgobj._subnode) do
				_so[_i] = wrap(_j,newadd)		
			end
			self._orgobj._subnode={}
			self._newobj=_so			
		else
		end
	end
	self._subobj=false
end

function NodeTreeTrans:_delete(key)
	local a,b=self:__is_proto_val(key)
	if (not a) and (b~=nil) then
		if self._newobj and self._newobj[key]~=nil then
			self._newobj[key] = nil
		end
		if self._orgobj[key]==nil then
			--delete a item which create in current trans
			if self._delkeys then self._delkeys[key] = nil end
		else
			if not self._delkeys then self._delkeys={} end
			self._delkeys[key] = true
		end
	end
end


function NodeTreeTrans:__set_field(fds,k,v)
	_edit(self,k,v)
end

-----TODO
function NodeTreeTrans:_setsub(cds,k,v)
	local _t=wrap(v)
	_edit(self,k,_t)
	return _t
end

function NodeTreeTrans:__get_child(k)
	if self._delkeys and self._delkeys[k] then return nil end
	if self._newobj then
		i=self._newobj[k]
		if i~=nil then if isNodeTreeTrans(i) then return i else return nil end end
	end
	return self:__autowrapsub(k)
end

function NodeTreeTrans:__remove_child(key,deep)
	_edit(self,key,nil)
end

function NodeTreeTrans:__add_child(key, mc,s)
	if self:_index_r(key) then return nil, 'ESN1E:'..key end
	local _t=wrap(s or self:__make_child(key,mc),true)
	self[key] = _t
	return _t
end

function NodeTreeTrans:_discard(deep)
	self._newobj=false
	self._delkeys=false
	if deep and self._subobj then
		for key, value in pairs(self._subobj) do
			value:_discard(deep)
		end
		self._subobj=false
	end
end

function NodeTreeTrans:_commit(deep,rec,dbrec,newflag)
	--print(self.__id)
	-- not auto create dbrec
	local dbcl = dbrec
	local savedel= dbrec and {}
	local dbncg = dbrec and {del={},chg={}}
	local dbcg = dbncg and dbncg.chg
	local dbdel = dbncg and dbncg.del

	local cl = rec or zdc.chglog.new()
	local ncg = {del={},chg={}}
	local cg = ncg.chg
	local del = ncg.del
	local saveflag,haschg
	local fields = self._fds
	local org = self._orgobj
	local neo = self._newobj
	local subobj = self._subobj
	local raw = org.__raw_v or org
	if self._delkeys then
		for key,value in pairs(self._delkeys) do
			local _t = org._subnode[key]
			if _t then
				local __ot=_t
				if dbrec then
					local _,fd_=org:__get_child_define(key)
					if fd_ and fd_.vir then
						-- vir not need to save
					else				
						savedel[#savedel+1]=__ot
						dbcl:_del(__ot)
					end
				end
				org.__dpl:_del_cache(_t.__id,true)
				cl:_del(__ot)
			elseif not newflag then
				local b = self:__get_pto_val(key)
				
				if b~=nil then
					if org[key]~=b then
						cg[key]=b
						haschg=true
						if dbrec then
							local fd_=org:__get_field_define(key)
							if not(fd_ and fd_.vir) then			
								dbcg[key]=b
								saveflag = true
							end
						end
					end
				else
					if raw[key]~=nil then
						haschg=true
						--cl:_del(key)
						del[key]=raw[key]
						if dbrec then
							local fd_=org:__get_field_define(key)
							if not(fd_ and fd_.vir) then			
								del[key]=raw[key]
								saveflag = true
							end
						end				
					end
				end
			end
			org._subnode[key] = nil
	
			if subobj then subobj[key] = nil end
			raw[key] = nil
			if neo then neo[key] = nil end
		end
	end
	local newfilter={}
	if neo then
		for key, value in pairs(neo) do
			if isNodeTreeTrans(value) then
				local _t = org._subnode[key]
				local _,fd_=org:__get_child_define(key)
				local _lorb=value._orgobj
				if _t then
					if dbrec then
						if fd_ and fd_.vir then
							-- vir not need to save
						else
							dbcl:_del(_t)
							savedel[#savedel+1]=_t
						end
					end
					cl:_del(_t)
					org.__dpl:_del_cache(_t.__id,true)
				end
				if dbrec then
					if fd_ and fd_.vir then
							-- vir not need to save
					else
						dbcl:_add(_lorb)
					end
				end
				cl:_add(_lorb)
				newfilter[_lorb.__id]=true
				if not subobj then
					subobj={}
					self._subobj=subobj
				end
				subobj[key] = value
				org._subnode[key] = _lorb
				org.__dpl:_put_cache_tree(_lorb)
			else
				if not newflag then		
					if org[key]~=value then
						haschg=true
						cg[key]=value
					end
					if raw[key] ~= value then
						raw[key] = value
						if dbrec then
							local fd_=org:__get_field_define(key)
							if fd_ and fd_.vir then
								-- vir not need to save
							else
								dbcg[key]=value
								saveflag=true
							end
						end
					end
				else
					if raw[key] ~= value then
						raw[key] = value
					end
				end			
			end
		end
		self._newobj=false
	end
	self._delkeys=false
	if haschg then
		cl:_chg(org,ncg)
	end
	if saveflag then
		dbcl:_chg(org,dbncg)
	end
	if deep and subobj then
		for key, value in pairs(subobj) do
			if value:_changed_guess() then
				local subdbcl=dbcl
				if subdbcl then 
					local _,fd_=org:__get_child_define(key)
					if fd_ and fd_.vir then
						subdbcl=nil
					else
					end
				end
				value:_commit(deep,cl,subdbcl,newflag or newfilter[value.__id])
			end
		end
	end
	
	saveflag = saveflag or (dbrec and self._orgobj.__needsave)
	--savedel = savedel or self._orgobj.__needdelsub
	if not savedel or #savedel==0 then savedel=false end

	rawset(org.__org,'__needdelsub',savedel or false)
	rawset(org.__org,'__needsave',saveflag or false)
	return cl,dbcl
end

function NodeTreeTrans:__save_ta()
	
end
function NodeTreeTrans:__remove_ta(deep)
	local par,key=zdc.idutil.GetParent(self.__id)
	local pn=self.__dpl:_get_node(par)
	if pn then pn:__remove_child(k,true) end
end

function NodeTreeTrans:__remove_subnodes_ta()
	assert(nil,'not impl NodeTreeTrans:__remove_subnodes_ta')
end

function NodeTreeTrans:__save(deep)
	--print('save',self.__id,self._orgobj.__needsave)
	local _org=self._orgobj
	if _org.__needdelsub then
		local tdl=_org.__needdelsub
		_org.__needdelsub = false
		for _,v in ipairs(tdl) do
			self.__dpl:_remove_node(v.__id,true)
		end
	end
	if _org.__needsave then
		_org.__needsave = false
		--_org:_save()
		self.__dpl:_save_node(self)
	end
	if deep and self._subobj then
		for k,v in pairs(self._subobj) do
			v:__save(deep)
		end
		self._subobj=false
	end
end

function NodeTreeTrans:_shrink()
	local _so=self._subobj
	if _so then
		local tmp={}
		for k,v in pairs(_so) do
			if v:_shrink() then
				tmp[#tmp+1]=k
				--print('shrink',self.__id,k)
			end
		end
		for k=1,#tmp do
			_so[tmp[k]]=nil
		end
		if not next(_so) then
			self._subobj=false
		end
	end
	return not (self._newobj or self._delkeys or self._subobj)
end

function NodeTreeTrans:_changed(deep)
	local chg = (self._newobj and next(self._newobj)) or (self._delkeys and next(self._delkeys))
	if chg then return true end
	if deep and self._subobj then
		for k,v in pairs(self._subobj) do
			if v:_changed(deep) then return true end
		end
	end
	return false
end

function NodeTreeTrans:_changed_guess()
	return self._newobj or self._delkeys or self._subobj
end

local function getshowval(t,k)
	local i
	if t._newobj then
		i = t._newobj[k]
		if i~=nil then return i end
	end
	if t._subobj then
		i=t._subobj[k]
		if i~=nil then return i end
	end
	return t._orgobj[k]
end

function NodeTreeTrans:__is_proto_val(key)
	if self._delkeys and self._delkeys[key] then return true, self:_index_r(key) end
	local i
	if self._newobj then
		i=self._newobj[key]
		if i~=nil then return false,i end
	end
	
	return self._orgobj:__is_proto_val(key)
end


function NodeTreeTrans:__get_flag_val(k,nopto)
	if self._delkeys and self._delkeys[k] then return '-', getshowval(self,k) end
	local i
	if self._newobj then
		i=self._newobj[k]	
		if i~=nil then
			if self._orgobj[k]==nil then
				return '+',i
			else
				if isNodeTreeTrans(i) then return '^',i end
				return '*',i
			end
		end
	end
	if self._subobj then
		i=self._subobj[k]
		if i~=nil then return '',i end
	end
	local p,i=self:__is_proto_val(k)
	if nopto then
		if p then return '',nil end
		return '',i
	else
		if p then return '@',i end
		return '', i
	end
end

function NodeTreeTrans:__get_other_fields(of)
	local f = {}
	of = of or {}
	local _f = self._fds
	
	for k,_ in pairs(self._orgobj.__raw_v) do
		if not (self._delkeys and self._delkeys[k]) and not _f[k] and not f[k] then 
			f[k] = true
			of[#of+1]=k
		end
	end

	if self._newobj then
		for k,_ in pairs(self._newobj) do
			if not _f[k] and not f[k] and not isNodeTreeTrans(v) then 
				f[k] = true
				of[#of+1]=k
			end
		end
	end
	return of
end

function NodeTreeTrans:__get_flag_val_withdel(k,nopto)
	if self._delkeys and self._delkeys[k] then return '-', getshowval(self,k) end
	local i
	if self._newobj then
		i=self._newobj[k]	
		if i~=nil then
			if self._orgobj[k]==nil then
				return '+',i
			else
				if isNodeTreeTrans(i) then return '^',i end
				return '*',i
			end
		end
	end
	if self._subobj then
		i=self._subobj[k]
		if i~=nil then return '',i end
	end
	local p,i=self:__is_proto_val(k)
	if nopto then
		if p then return '',nil end
		return '',i
	else
		if p then return '@',i end
		return '', i
	end
end

function NodeTreeTrans:__get_other_fields_withdel(of,withsub)
	local f = {}
	of = of or {}
	local _f = self._fds
	
	for k,_ in pairs(self._orgobj.__raw_v) do
		if not _f[k] and not f[k] then 
			f[k] = true
			of[#of+1]=k
		end
	end
	if self._delkeys then
		for k,_ in pairs(self._delkeys) do
			if not _f[k] and not f[k] then 
				f[k] = true
				of[#of+1]=k 
			end		
		end
	end	
	if withsub then
		if self._subobj then 
			for k,_ in pairs(self._subobj) do
				if not _f[k] and not f[k] then 
					f[k] = true
					of[#of+1]=k
				end
			end
		end
		for k,_ in pairs(self._orgobj._subnode) do
			if not _f[k] and not f[k] then 
				f[k] = true
				of[#of+1]=k
			end
		end
	end
	
	if self._newobj then
		for k,v in pairs(self._newobj) do
			if not _f[k] and not f[k] and (withsub or (not isNodeTreeTrans(v))) then 
				f[k] = true
				of[#of+1]=k
			end
		end
	end
	return of
end

function NodeTreeTrans:__check_nil_fields(deep,helpall)
	local nilf=nil
	for field_name, field in pairs(self._fds) do
		if string.byte(field_name)~=95 then
			local ff=self[field_name]
			if ff==nil then
				nilf=nilf or {}
				nilf[#nilf+1]=field_name
				if not helpall then return nilf end
			end
		end
	end
	if self._newobj then
		for k,v in pairs(self._newobj) do
			if not (self._delkeys and self._delkeys[k]) and isNodeTreeTrans(v) then 
				local _t=v:__check_nil_fields(deep,helpall)
				if _t then
					nilf=nilf or {}
					nilf[#nilf+1]=_t
					if not helpall then return nilf end
				end 
			end
		end
	end
	
	--TODO: not need to check?
	if self._subobj then
		for k,v in pairs(self._subobj) do
			if not (self._delkeys and self._delkeys[k]) and not of[k] then 
				local _t=v:__check_nil_fields(deep,helpall)
				if _t then
					nilf=nilf or {}
					nilf[#nilf+1]=_t
					if not helpall then return nilf end
				end 
			end
		end
	end
	return nilf
end

function NodeTreeTrans:__get_child_all(of)
	local of = of or {}
	if self._newobj then
		for k,v in pairs(self._newobj) do
			if not (self._delkeys and self._delkeys[k]) and isNodeTreeTrans(v) then 
				of[k]=v 
			end
		end
	end

	for k,v in pairs(self._orgobj._subnode) do
		if not (self._delkeys and self._delkeys[k]) and not of[k] then
			of[k]=self:__autowrapsub(k)
		end
	end
	return of
end

function NodeTreeTrans:__get_child_key_list()
	local f = {}
	local of = {}
	if self._newobj then
		for k,v in pairs(self._newobj) do
			if not (self._delkeys and self._delkeys[k]) and isNodeTreeTrans(v) then 
				f[k] = true
				of[#of+1]=k
			end
		end
	end
	for k,_ in pairs(self._orgobj._subnode) do
		if not (self._delkeys and self._delkeys[k]) and not f[k] then 
			f[k] = true
			of[#of+1]=k
		end
	end
	return of
end


function NodeTreeTrans:__get_child_r(k)
	if self._delkeys and self._delkeys[k] then return nil end
	if self._newobj then
		local i=self._newobj[k]
		if i~=nil then if isNodeTreeTrans(i) then return i else return nil end end
	end
	if self._subobj then
		local i=self._subobj[k]
		if i then return i end
	end
	return self._orgobj._subnode[key]
end

function NodeTreeTrans:__enumerate_child_r(cb)
	local of = {}
	if self._newobj then
		for k,v in pairs(self._newobj) do
			if not (self._delkeys and self._delkeys[k]) and isNodeTreeTrans(v) then
				of[k]=true
				local r1,r2=cb(self,k,v)
				if r1 then return r1,r2 end
			end
		end
	end
	if self._subobj then
		for k,v in pairs(self._subobj) do
			if not (self._delkeys and self._delkeys[k]) and not of[k] then
				of[k]=true 
				local r1,r2=cb(self,k,v)
				if r1 then return r1,r2 end
			end
		end
	end
	for k,v in pairs(self._orgobj._subnode) do
		if not (self._delkeys and self._delkeys[k]) and not of[k] then
			local r1,r2=cb(self,k,v)
			if r1 then return r1,r2 end
		end
	end
end
function NodeTreeTrans:__get_child_all_r(of)
	
end
